#include <iostream>
using std::cout;
using std::endl;
using std::cerr;
#include <utility>
#include <assert.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include "load_and_show_screen.h"
#include "lods_file_io.h"
#include "font_related.h"
#include "vect.h"

const std::pair<vect,vect> load_and_show_screen::obj_pos[] = 
{ std::pair<vect,vect>(vect(-1.0f,-1.0f,-1.0f),vect(1.0f,1.0f,1.0f)),
  
  std::pair<vect,vect>(vect(-1.0f,-0.5f,-0.5f),vect(0.0f,0.5f,0.5f)),
  std::pair<vect,vect>(vect(0.0f,-0.5f,-0.5f),vect(1.0f,0.5f,0.5)),

  std::pair<vect,vect>(vect(-1.0f,0.0f,-0.5f),vect(0.0f,1.0f,0.5f)),
  std::pair<vect,vect>(vect(0.0f,0.0f,-0.5f),vect(1.0f,1.0f,0.5f)),
  std::pair<vect,vect>(vect(-1.0f,-1.0f,-0.5f),vect(0.0f,0.0f,0.5f)),
  std::pair<vect,vect>(vect(0.0f,-1.0f,-0.5f),vect(1.0f,0.0f,0.5f)),
};

const std::pair<float,float> load_and_show_screen::label_pos[] =
{ std::pair<float,float>(0.02,0.7),

  std::pair<float,float>(0.02,0.7), 
  std::pair<float,float>(0.85,0.7),

  std::pair<float,float>(0.02,0.75),
  std::pair<float,float>(0.85,0.75),
  std::pair<float,float>(0.02,0.4),
  std::pair<float,float>(0.85,0.4)
};

const int load_and_show_screen::obj_pos_idx[] =
{ 0,//this position is not used, for convience from 1 indexing is used
  0,
  1,
  3,3,
  7,7,7,7
};



int load_and_show_screen::keyboard_func(unsigned char key, int x, int y)
{
   if(this->userEnteringValue)
   {
      if(key==13)//enter
      {
         userEnteringValue=false;
         if(filename.length()>0)
         {
            if(lods.size()>0)
            {
               // delete previous scene
               lods.clear();
               loaded_filename.clear();
            }

            int ret;
            ret = load_lods(filename,lods);
            if(ret!=0) {
               // just to be sure
               lods.clear();
               //todo
               std::cerr<<"File "<<filename<<" NOT loaded."<<std::endl;
               filename.clear();
            }
            else
               loaded_filename = filename;

            filename.clear();
            find_ranges();
            /*for(unsigned i=0; i<ranges.size(); i++)
            {
               cerr<<ranges[i].first.x<<" "<<ranges[i].second.x<<endl;
               cerr<<ranges[i].first.y<<" "<<ranges[i].second.y<<endl;
               cerr<<ranges[i].first.z<<" "<<ranges[i].second.z<<endl;
            }*/
            find_transl_to_origin();
            //for(unsigned i=0; i<transl_to_origin.size(); i++)
               //cerr<<transl_to_origin[i].x<<"   "<<transl_to_origin[i].y<<"   "<<
                  //transl_to_origin[i].z<<endl;
            
            reset_view();
            /*for(unsigned i=0; i<dispo.displayed_count(); i++) {
               vect v = fit.get_transl(i);
               cerr<<v.x<<"  "<<v.y<<"  "<<v.z<<endl;
               cerr<<fit.get_scale(i)<<endl;
            }*/
            return 0;
         }
      }
      else if(key==27) //escape
      {
         userEnteringValue=false;
         filename.clear();
         return 0;
      }
      else if(key=='\b')
      {
         if(filename.length()>0)
            filename = filename.substr(0,filename.length()-1);
         return 0;
      }
      else if(key>='a'&&key<='z' || key>='A'&&key<='Z' || key>='0'&&key<='9' || 
              key==' ' || key=='/' || key=='\\' || key=='.' || key=='_')
      {
         if(filename.length()<(unsigned)MAX_FILENAME_LENGTH)
            filename += key;
         return 0;
      }
   }
   else// user is not entering file name
   {
      if(key==13) //enter
      {
         // should be clear already, just in case
         filename.clear();
         userEnteringValue=true;
         return 0;
      }
      else if(key==27) //escape
         return key;
      
      else if(key=='z') { // scene rotation
         mouseMode = mouse_mode::rotate; 
         //mouseOn = false;
      }
      else if(key=='x') { // scene zoom
         mouseMode = mouse_mode::zoom; 
         //mouseOn = false;
      }
      else if(key=='c') { // scene pan
         mouseMode = mouse_mode::move; 
         //mouseOn = false;
      }
      else if(key=='m') {
         change_polygon_mode();
      }
      else if(key=='r') {
         reset_view();
      }
   }

   // don't care about any uncaptured keys
   return 0;
}

void load_and_show_screen::mouse_func(int button, int state, int x, int y)
{
   mouseOn = !mouseOn;
   prev_mouse_x = x;
   prev_mouse_y = y;
}

void load_and_show_screen::mouse_motion_func(int x, int y)
{
   if(dispo.displayed_count() <= 0 || mouseOn==false)
      return;
   int xchange = x - prev_mouse_x;
   int ychange = y - prev_mouse_y;
   // cancel too rapid changes
   if(xchange>30 && ychange>30)
      return;

   if(mouseMode == mouse_mode::rotate)
   {
      if(dispo.whole_scene_active())
      {
         scenerot.x += ychange*pixelChangeRot;
         scenerot.y += xchange*pixelChangeRot;
         if(scenerot.x > 360.0)
            scenerot.x -= 360.0;
         else if(scenerot.x < 0.0)
            scenerot.x += 360.0;

         if(scenerot.y > 360.0)
            scenerot.y -= 360.0;
         else if(scenerot.y < 0.0)
            scenerot.y += 360.0;
      }
      
   }
   else if(mouseMode == mouse_mode::zoom)
   {
      float change = std::sqrt(xchange*xchange + ychange*ychange);
      change *= pixelChangeZoom;
      if(xchange>0 && ychange<0) {
         scenezoom += change;
      }
      else if(xchange<0 && ychange>0) {
         scenezoom -= change;
      }
   }
   else if(mouseMode == mouse_mode::move)
   {
      scenetransl.x += xchange*pixelChangeTransl;
      scenetransl.y -= ychange*pixelChangeTransl;
   }

   prev_mouse_x = x;
   prev_mouse_y = y;
}


void load_and_show_screen::display_func(int width, int height)
{
   int cheight=height-20;

   PushOrtho(width, height); 
   this->light.disable();

   if(userEnteringValue)
      this->setActMenuColor();
   else
      this->setMenuColor();
   if(userEnteringValue)
   {
      char tab[this->MAX_FILENAME_LENGTH + 20];
      assert(filename.length()<=(unsigned)MAX_FILENAME_LENGTH);
      sprintf(tab,"Load the object: %s", filename.c_str());
      DrawString(tab, 10, cheight);
   }
   else
      DrawString("Load an object", 10, cheight);


   //loaded_filename="Kopek";
   this->setMenuColor();
   if(loaded_filename.size()>0)
   {
      char tab[this->MAX_FILENAME_LENGTH + 10];
      sprintf(tab, "Object: %s", loaded_filename.c_str());
      DrawString(tab, width/5, 10);
   }

   PopOrtho();


   draw3d(width,height);
}

void load_and_show_screen::draw3d(int width, int height)
{
   if(dispo.displayed_count() > 0)
   {
         // draw bounding boxes
      light.disable();
      
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();

      if(dispo.displayed_count() == 1)
                           //todo
         glTranslatef(0.f,0.f,-3.f);
      else if(dispo.displayed_count() == 2)
                           //todo
         glTranslatef(0.f,0.f,-1.8f);
      else
                           //todo
         glTranslatef(0.f,0.f,-2.4f);

      glScalef(scenezoom,scenezoom,scenezoom);
      glRotatef(scenerot.x,1.0,0.0,0.0);
      glRotatef(scenerot.y,0.0,1.0,0.0);
      glTranslatef(scenetransl.x,scenetransl.y,scenetransl.z);

      int sti=obj_pos_idx[dispo.displayed_count()];
      std::vector<unsigned> disp(dispo.displayed_objects());
      for(unsigned i=0; i<dispo.displayed_count(); i++)
      {
         PushOrtho(width,height);
         {
            setMenuColor();
            int x = int(label_pos[sti+i].first*width);
            int y = int(label_pos[sti+i].second*height);
            char msg[64];
            const object3dv<vertex>& o = lods[disp[i]];

            // the is no risk of msg overflow because of
            // available values range of int type
            sprintf(msg, "LOD %d", i);
            DrawString(msg,x+35,y);

            y-=20;
            sprintf(msg, "vertices:  %d", o.vcount);
            DrawString(msg,x,y);
            y-=20;
            sprintf(msg, "triangles: %d", o.tcount);
            DrawString(msg,x,y);
         }
         PopOrtho();

         const vect& min=obj_pos[sti+i].first;
         const vect& max=obj_pos[sti+i].second;

         //front face
         glColor3f(.25f,.64f,.81f);
         glBegin(GL_LINE_STRIP);
            glVertex3f(min.x,min.y,max.z);
            glVertex3f(min.x,max.y,max.z);
            glVertex3f(max.x,max.y,max.z);
            glVertex3f(max.x,min.y,max.z);
            glVertex3f(min.x,min.y,max.z);
         glEnd();

         glBegin(GL_LINES);
            glColor3f(.25f,.64f,.81f);
            glVertex3f(min.x,min.y,max.z);
            glColor3f(0.98f,0.1f,0.2f);
            glVertex3f(min.x,min.y,min.z);

            glColor3f(.25f,.64f,.81f);
            glVertex3f(min.x,max.y,max.z);
            glColor3f(0.98f,0.1f,0.2f);
            glVertex3f(min.x,max.y,min.z);

            glColor3f(.25f,.64f,.81f);
            glVertex3f(max.x,max.y,max.z);
            glColor3f(0.98f,0.1f,0.2f);
            glVertex3f(max.x,max.y,min.z);

            glColor3f(.25f,.64f,.81f);
            glVertex3f(max.x,min.y,max.z);
            glColor3f(0.98f,0.1f,0.2f);
            glVertex3f(max.x,min.y,min.z);
         glEnd();

         //back face
         glColor3f(0.98f,0.1f,0.2f);
         glBegin(GL_LINE_STRIP);
            glVertex3f(max.x,min.y,min.z);
            glVertex3f(max.x,max.y,min.z);
            glVertex3f(min.x,max.y,min.z);
            glVertex3f(min.x,min.y,min.z);
            glVertex3f(max.x,min.y,min.z);
         glEnd();
      }


      {
            // draw symbolic light
         light.disable();
         glPushMatrix();
         glColor3f(0.8f,1.0f,0.0f);
         glTranslatef(light.lights[0].pos.pos[0],
                      light.lights[0].pos.pos[1],
                      light.lights[0].pos.pos[2]);
         glutWireSphere(0.05,6,6);
         glPopMatrix();  

            // draw LODs
         light.enable();
         glLightfv(GL_LIGHT0, GL_POSITION, light.lights[0].pos.pos);
         //int sti=obj_pos_idx[dispo.displayed_count()];
         //std::vector<unsigned> disp(dispo.displayed_objects());
         unsigned lend = disp.size();
         for(unsigned li=0; li<lend; li++)
         {
            glPushMatrix();

            unsigned o = disp[li];
            GLfloat rescale = fit.get_scale(li);
            vect transl = fit.get_transl(li);
            
            glTranslatef(transl.x, transl.y, transl.z);
            //todo rotate
            glScalef(rescale,rescale,rescale);
            glTranslatef(transl_to_origin[o].x, transl_to_origin[o].y, transl_to_origin[o].z);

            if(dispo.active_object(li))
               setActiveObjColor();
            else
               setObjColor();
               
            //if(light.enabled())
            {
               GLfloat col[4];
               col[0]=0.8;
               col[1]=0.8;
               col[2]=0.8;
               col[3]=1.0;
               glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,col);
               col[0]=0.0;
               col[1]=0.0;
               col[2]=0.0;
               col[3]=1.0;
               glMaterialfv(GL_FRONT_AND_BACK,GL_EMISSION,col);
               glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,50.0);

               // todo maybe it's worth to employ all materials?
               // for now, set only the first material for the whole object
               if(lods[o].mcount>0 && dispo.active_object(li)==false)
               {
                  col[0]=lods[o].mats[0].diffuse[0];
                  col[1]=lods[o].mats[0].diffuse[1];
                  col[2]=lods[o].mats[0].diffuse[2];
                  col[3]=1.0f;
                  glColor4fv(col);

                  col[0]=lods[o].mats[0].specular[0];
                  col[1]=lods[o].mats[0].specular[1];
                  col[2]=lods[o].mats[0].specular[2];
                  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, col);
               }
            }

            glBegin(GL_TRIANGLES);
            {
               int endj=lods[o].tcount;
               vertex* v;

               for(int j=0; j<endj; j++)
               {
                  
                  v = &(lods[o].vects)[(lods[o].tris)[j].verts[0]];
                  glNormal3fv(v->normal);
                  glVertex3fv(v->xyz);
                  v = &(lods[o].vects)[(lods[o].tris)[j].verts[1]];
                  glNormal3fv(v->normal);
                  glVertex3fv(v->xyz);
                  v = &(lods[o].vects)[(lods[o].tris)[j].verts[2]];
                  glNormal3fv(v->normal);
                  glVertex3fv(v->xyz);
               }
            }
            glEnd();

            /*glTranslatef(-4.0,0.0,0.0);
            glScalef(0.005,0.005,0.005);
            glutStrokeCharacter(GLUT_STROKE_ROMAN, 'c');*/
            
            glPopMatrix();
         }
      }

      glPopMatrix();
   }
}

void load_and_show_screen::reset_view()
{
   mouseMode = mouse_mode::rotate;
   mouseOn = false;
   scenezoom = 1.0;
   scenerot = vect(0.0,0.0,0.0);
   scenetransl = vect(0.0,0.0,0.0);

   this->dispo.reset();
   unsigned end = lods.size();
   unsigned i=0;
   for(; i<end && i<(unsigned)MAX_DISPLAYED_LODS; i++)
      this->dispo.put_on_display(i,i);

   
   int sti=obj_pos_idx[dispo.displayed_count()];
   for(i=0; i<end && i<(unsigned)MAX_DISPLAYED_LODS; i++)
      fit.put(i,i, obj_pos[sti+i].first, obj_pos[sti+i].second,
              ranges[i].first, ranges[i].second);
}

void load_and_show_screen::find_ranges()
{
   unsigned lend=lods.size();
   ranges.resize(lend);
   for(unsigned li=0; li<lend; li++)
   {
      unsigned end = lods[li].vcount;
      assert(end==lods[li].vects.size());

      float& minx = ranges[li].first.x;
      float& maxx = ranges[li].second.x;
      float& miny = ranges[li].first.y;
      float& maxy = ranges[li].second.y;
      float& minz = ranges[li].first.z;
      float& maxz = ranges[li].second.z;

      minx = INFINITY_COORDINATE;// will store minimum
      maxx = INFINITY_COORDINATE;// and maximum
      maxx = -maxx;
      miny = INFINITY_COORDINATE;
      maxy = INFINITY_COORDINATE;
      maxy = -maxy;
      minz = INFINITY_COORDINATE;
      maxz = INFINITY_COORDINATE;
      maxz = -maxz;

      const std::vector<vertex>& vects = lods[li].vects;
      for(unsigned i=0; i<end; i++)
      {
         if(vects[i].xyz[0] < minx) // x
            minx = vects[i].xyz[0];
         if(vects[i].xyz[0] > maxx)
            maxx = vects[i].xyz[0];
         if(vects[i].xyz[1] < miny) // y
            miny = vects[i].xyz[1];
         if(vects[i].xyz[1] > maxy)
            maxy = vects[i].xyz[1];
         if(vects[i].xyz[2] < minz) // z
            minz = vects[i].xyz[2];
         if(vects[i].xyz[2] > maxz)
            maxz = vects[i].xyz[2];
      }
   }
}

void load_and_show_screen::find_transl_to_origin()
{
   if(ranges.size() <= 0)
      return;
   unsigned end=ranges.size();
   transl_to_origin.resize(end);
   for(unsigned i=0; i<end; i++)
   {
      const vect& min = ranges[i].first;
      const vect& max = ranges[i].second;
      // -(min.x + (max.x-min.x)/2)
      transl_to_origin[i].x = (min.x-max.x)/2 - min.x;
      transl_to_origin[i].y = (min.y-max.y)/2 - min.y;
      transl_to_origin[i].z = (min.z-max.z)/2 - min.z;
   }
}

void load_and_show_screen::change_polygon_mode()
{
   if(polygon_mode==GL_FILL)
      polygon_mode=GL_LINE;
   else
      polygon_mode=GL_FILL;
   glPolygonMode(GL_FRONT_AND_BACK, polygon_mode);
}
